package com.zen.lon.open.remote.support;

import com.sun.istack.internal.NotNull;
import com.zen.lon.open.proxy.ProxyAgentClassManager;
import com.zen.lon.open.proxy.ProxyAgentFactoryBean;
import com.zen.lon.open.remote.annotation.RemoteClient;
import com.zen.lon.open.remote.condition.ProxyCondition;
import com.zen.lon.open.remote.exp.RemoteClientException;
import com.zen.lon.open.remote.spi.ServiceBootstrap;
import com.zen.lon.open.remote.utils.LogUtils;
import java.beans.Introspector;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import lombok.SneakyThrows;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;

/**
 * RemoteClient注册器
 *
 * @author ZonLen since on 2021/6/29 下午7:50
 */
public class RemoteClientRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {

  private static final String FEIGN_CLIENT = "org.springframework.cloud.openfeign.FeignClient";

  private static final List<ProxyCondition> REMOTE_CLIENT_REGISTRAR_CONDITIONS = new ArrayList<>();

  @Override
  public void setEnvironment(@NotNull Environment environment) {
    final Iterator<ProxyCondition> iterator = ServiceBootstrap
        .loadAll(ProxyCondition.class);
    while (iterator.hasNext()) {
      final ProxyCondition condition = iterator.next();
      condition.setEnvironment(environment);
      REMOTE_CLIENT_REGISTRAR_CONDITIONS.add(condition);
    }
  }

  /**
   * 将RemoteApi代理注册到spring容器
   */
  @SneakyThrows
  @Override
  public void registerBeanDefinitions(@NotNull AnnotationMetadata metadata,
      @NotNull BeanDefinitionRegistry registry) {
    LogUtils.info(RemoteClientAutoConfiguration.class, "Remote Client start up!!!");
    final RemoteClientScanner scanner = new RemoteClientScanner(registry);
    for (String basePackage : scanner.getBasePackages(metadata)) {
      Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
      for (BeanDefinition candidateComponent : candidateComponents) {
        ProxyAgentClassManager.add(candidateComponent.getBeanClassName());
      }
      for (BeanDefinition candidateComponent : candidateComponents) {
        final Class<?> remoteClientClass = ProxyAgentClassManager
            .getClassByName(candidateComponent.getBeanClassName());
        if (remoteClientClass.isInterface()) {
          if (Arrays.stream(remoteClientClass.getAnnotations()).map(Annotation::getClass)
              .map(Class::getName)
              .anyMatch(annotationName -> annotationName.equals(FEIGN_CLIENT))) {
            throw new RemoteClientException(
                "This proxy bean [" + candidateComponent.getBeanClassName()
                    + "] must has only one annotation, either @FeignClient or @RemoteClient");
          }
          final boolean proxyCondition = postProxyCondition(remoteClientClass);
          if (proxyCondition) {
            registerRemoteClient(registry, candidateComponent, remoteClientClass);
          }
        }
      }
    }
  }

  private boolean postProxyCondition(Class<?> remoteClientClass) {
    final RemoteClient remoteClient = remoteClientClass.getAnnotation(RemoteClient.class);
    if (null == remoteClient) {
      return false;
    }
    final List<Class<? extends ProxyCondition>> conditions = Collections
        .singletonList(remoteClient.proxyCondition());
    return REMOTE_CLIENT_REGISTRAR_CONDITIONS.stream()
        .filter(c -> conditions.stream()
            .anyMatch(conditionClass -> conditionClass.isAssignableFrom(c.getClass())))
        .allMatch(c -> c.eligible(remoteClientClass));
  }

  private void registerRemoteClient(BeanDefinitionRegistry registry,
      BeanDefinition candidateComponent,
      Class<?> remoteClientClass) {
    String className = candidateComponent.getBeanClassName();
    BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder
        .genericBeanDefinition(ProxyAgentFactoryBean.class)
        .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
        .addConstructorArgValue(remoteClientClass)
        .setPrimary(true);
    BeanDefinitionHolder holder = new BeanDefinitionHolder(definitionBuilder.getBeanDefinition(),
        generateBeanName(className));
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
  }

  private String generateBeanName(String beanClassName) {
    String shortClassName = ClassUtils.getShortName(beanClassName);
    return Introspector.decapitalize(shortClassName);
  }


}
